/*
  S.M.A.C.K - An operating system kernel
  Copyright (C) 2010,2011 Mattias Holm and Kristian Rietveld
  For licensing and a full list of authors of the kernel, see the files
  COPYING and AUTHORS.
*/

#include <panic.h>
#include <ctype.h>
#include <stddef.h>
#include <string.h>

#include <stdio.h>
#include <assert.h>
#include <vm.h>
#include <bittools.h>
#include <slaballoc.h>

void
abort(void)
{
  panic();
}


static int
validchar(int c, int base)
{
  if (base <= 10) {
    if ('0' <= tolower(c) && tolower(c) < '0' + base) return 1;
    return 0;
  }

  if ('0' <= tolower(c) && tolower(c) < '0' + 10) return 1;
  if ('a' <= tolower(c) && tolower(c) < 'a' + base - 10) return 1;
  return 0;
}

static int
intfromchar(int c)
{
  if ('0' <= c && c <= '9') return tolower(c) - '0';
  return tolower(c) - 'a' + 10;
}


long
strtol(const char *restrict str, char **restrict end, int base)
{
  while (isspace(*str)) str ++; // Skip WS

  // Positive or negative
  int pn = 1;
  if (*str == '+') {
    str ++; pn = 1;
  } else if (*str == '-') {
    str ++; pn = -1;
  }

  // Get effective base
  if (*str == '0' && tolower(*(str+1)) == 'x') {
    str += 2;
    if (base == 0 || base == 16) base = 16;
    else return 0;
  } else if (*str == '0' && tolower(*(str+1)) == 'b') {
    // NOTE: 0b is an extension to the ISO version of this function
    str += 2;
    if (base == 0 || base == 2) base = 2;
    else return 0;
  } else if (*str == '0') {
    str ++;
    if (base == 0 || base == 8) base = 8;
    else return 0;
  } else if (base == 0) {
    base = 10;
  }

  long result = 0;
  while (validchar(*str, base)) {
    result *= base;
    result += intfromchar(*str);
    str ++;
  }

  if (end) *end = (char *restrict)str;

  return pn*result;
}

#define SLAB_COUNT 16
#define MAX_OBJ_SIZE 65536

slab_t slabs[SLAB_COUNT];

#ifdef MALLOC_DEBUG
#define DEBUG(s, ...) printf(s, __VA_ARGS__)
#else
#define DEBUG(s, ...)
#endif

// TODO: Move slab_* functions to separate file
slab_t
slab_create(size_t obj_size)
{
  assert(obj_size < 8*4096 - 8);
  return (slab_t){obj_size, NULL};
}

void*
slab_alloc(slab_t *slab)
{
  if (slab->first_free == NULL) {
    slab->first_free = vm_map(vm_get_kernel_map(VM_REG_KERNEL_HEAP), VM_SUPER_RW, 0, 4096*8);
    if (slab->first_free == NULL) return NULL;
    DEBUG("malloc dbg: new slab @ %p\n", slabs[slab_idx].first_free);

    unsigned slab_count = 4096*8/(sizeof(slab_obj_t)+slab->obj_size);
    for (unsigned i = 0 ; i < slab_count - 1 ; i ++) {
      ((slab_obj_t*)
        ((uint8_t*)(slab->first_free)+i*(sizeof(slab_obj_t)+slab->obj_size)))->u.next
        = (slab_obj_t*)
        ((uint8_t*)(slab->first_free)+(i+1)*(sizeof(slab_obj_t)+slab->obj_size));
    }
    ((slab_obj_t*)
     ((uint8_t*)(slab->first_free)+(slab_count-1)*(sizeof(slab_obj_t)+slab->obj_size)))
      ->u.next = NULL;
  }

  slab_obj_t *obj = slab->first_free;
  slab->first_free = obj->u.next;

  obj->u.slab = slab;
  return &obj->data;
}

void
slab_free(void *obj)
{
  if (obj == NULL) return;
  DEBUG("malloc dbg: slab_free %p\n", obj);

  slab_obj_t *slab_obj = obj - offsetof(slab_obj_t, data);
  slab_t *slab = slab_obj->u.slab;

  slab_obj->u.next = slab->first_free;
  slab->first_free = slab_obj;
}

void*
malloc(size_t size)
{
  if ((size_t)UINT32_MAX < size) return NULL;

  // Allocate from kernel heap
  if (size < sizeof(void*)) {
    size = sizeof(void*);
  }
  size = clp2_32(size);
  DEBUG("malloc dbg: alloc %d\n", (int)size);
  unsigned slab_idx = ctz_32(size);
  if (slab_idx >= SLAB_COUNT) return NULL;
  DEBUG("malloc dbg: slab idx %u\n", slab_idx);

  if (slabs[slab_idx].first_free == NULL && slabs[slab_idx].obj_size == 0) {
    slabs[slab_idx] = slab_create(size);
  }

  return slab_alloc(&slabs[slab_idx]);
}

void
free(void *obj)
{
  DEBUG("malloc dbg: free %p\n", obj);
  slab_free(obj);
}



void*
calloc(size_t count, size_t size)
{
  // TODO: Lazy page allocation
  void *data = malloc(count*size);
  memset(data, 0, count*size);
  return data;
}


void*
valloc(size_t size)
{
  // Alloc page aligned memory
  return NULL;
}

void*
kalloc(size_t size)
{
  // Allocate physically consectutive memory pages
  return NULL;
}
